#ifndef __PLANE_H__
#define __PLANE_H__

#include "vector3.h"


////////////////////////////////////////////////////////////////////////////////
// Constant Values
//////////////////

enum PLANE_SIDE {
  BEHIND      =  0,
  INTERSECTS  =  1,
  FRONT       =  2,

  SIDE_BACK   =  0,
  SIDE_ON     =  1,
  SIDE_FRONT  =  2,
  SIDE_CROSS  = -2
};

enum PLANE_AXIS {
  PLANE_X         = 0, // 0-2 are axial planes
  PLANE_Y         = 1,
  PLANE_Z         = 2,
  PLANE_ANYX      = 3, // 3-5 are non-axial planes snapped to the nearest
  PLANE_ANYY      = 4,
  PLANE_ANYZ      = 5,

  PLANE_NON_AXIAL = 3 // generic non-axial...
};

static const double  plane_epsilon = 1e-5;  // Used to compensate for floating point inaccuracy.


////////////////////////////////////////////////////////////////////////////////
// Function Prototypes
//////////////////////
inline int PlaneTypeForNormal (const e3dVector3& normal);


////////////////////////////////////////////////////////////////////////////////
// Class Definitions
////////////////////

class e3dPlane {
public:
            e3dPlane (void);
   virtual ~e3dPlane (void);

  /* ------------------------- */

  inline void CalculateType (void) {
    type = PlaneTypeForNormal (normal);
  }

  inline void FromPoints (const e3dVector3 &a, const e3dVector3 &b, const e3dVector3 &c)
  {
    CrossProduct ((c - a), (b - a), normal);
    normal.Normalize ();
    //VectorNormalize (normal);

    dist = /*-*/DotProduct (normal, a);

    ClassifyPlane ();
  }

  inline PLANE_AXIS ClassifyPlane (void)
  {
    vec_t ax,
          ay,
          az;

    if (normal [0] >= 1.0f)
      return PLANE_X;
    if (normal [1] >= 1.0f)
      return PLANE_Y;
    if (normal [2] >= 1.0f)
      return PLANE_Z;

    ax = (vec_t)fabs (normal [0]);
    ay = (vec_t)fabs (normal [1]);
    az = (vec_t)fabs (normal [2]);

    if ((ax >= ay) & (ax >= az))
      return PLANE_ANYX;
    if ((ay >= ax) & (ay >= az))
      return PLANE_ANYY;
    return PLANE_ANYZ;
  }

  inline PLANE_SIDE ClassifyPoint (const e3dVector3 &v)
  {
    vec_t fDist = Distance (v);

    if (fDist > plane_epsilon)
      return SIDE_FRONT;
    else if (fDist < -plane_epsilon)
      return SIDE_BACK;

    return SIDE_ON;
  }

  inline bool GetIntersection ( const e3dPlane& a,
                                const e3dPlane& b,
                                e3dVector3&     v) {
    vec_t denom;

    // Normal Cross Products
    e3dVector3 cross_ab; /* A    % B    */
    e3dVector3 cross_bn; /* B    % This */
    e3dVector3 cross_na; /* This % A    */

    CrossProduct (a.normal, b.normal, cross_ab);
    CrossProduct (b.normal, normal,   cross_bn);
    CrossProduct (normal,   a.normal, cross_na);

    denom = DotProduct (normal, cross_ab);

    if (fabs (denom) < plane_epsilon)
      return false;

    v = (cross_ab * -dist   -
         cross_bn *  a.dist -
         cross_na *  b.dist) / denom;

    return true;
  };

  inline bool GetIntersection ( const e3dVector3& Start,
                                const e3dVector3& End,
                                      e3dVector3& Intersection,
                                      vec_t&      Percentage )
  {
    e3dVector3 Direction = End - Start;
    vec_t      Num,
               Denom;

    Direction.Normalize ();

    Denom = DotProduct (normal, Direction);

    if (fabs (Denom) < plane_epsilon)
      return false;

    Num          = -Distance (Start);
    Percentage   = Num / Denom;
    Intersection = Start + (Direction * Percentage);
    Percentage  /= Length ((End - Start));

    return true;
  };

  inline vec_t Distance (const e3dVector3& v) {
    return DotProduct (v, normal) + dist;
  };

  /* ------------------------- */

//private:
  e3dVector3 normal;
  vec_t      dist;

  // Optimizations, NON-standard planar math.
  int        type;     // Speed up side tests
  int        signbits; // signx + (signy << 1) + (signz << 1)
};


////////////////////////////////////////////////////////////////////////////////
// Global Methods
/////////////////

// This returns the distance the plane is from the origin (0, 0, 0)
// It takes the normal to the plane, along with ANY point that lies on the plane (any corner)
inline
vec_t PlaneDistance ( const e3dVector3& vNormal,
                      const e3dVector3& vPoint )
{
  /* Distance: The negated dot product of the normal of the plane and the point. */
  return (- DotProduct (vNormal, vPoint));
}


inline
vec_t RayPlaneDistance ( const e3dVector3& vRayOrigin,
                         const e3dVector3& vRayNormal,
                         const e3dVector3& vPlaneNormal,
                         const vec_t&      fPlaneDistance )
{
  vec_t cosAlpha;
  vec_t deltaD;

  cosAlpha = DotProduct (vRayNormal, vPlaneNormal);

  // Parallel to the plane (alpha=90)
  if (! cosAlpha)
    return -1.0f;

  deltaD = fPlaneDistance - DotProduct (vRayOrigin, vPlaneNormal);
    
  return (deltaD / cosAlpha);
}


// This takes a triangle (plane) and line and returns true if they intersected
bool IntersectedPlane ( e3dVector3  vPoly [],
                        e3dVector3  vLine [],
                        e3dVector3 &vNormal,
                        vec_t      &originDistance );


// This function classifies a sphere according to a plane. (BEHIND, in FRONT, or INTERSECTS)
int ClassifySphere ( e3dVector3 &vCenter, 
                     e3dVector3 &vNormal,
                     e3dVector3 &vPoint,
                     vec_t       radius,
                     vec_t      &distance );


inline int PlaneTypeForNormal (const e3dVector3& normal)
{
  vec_t ax, ay, az;

  if ((normal [0] == 1.0f) | (normal [0] == -1.0f))
    return PLANE_X;
  if ((normal [1] == 1.0f) | (normal [1] == -1.0f))
    return PLANE_Y;
  if ((normal [2] == 1.0f) | (normal [2] == -1.0f))
    return PLANE_Z;

  ax = fabsf (normal [0]);
  ay = fabsf (normal [1]);
  az = fabsf (normal [2]);

  if ((ax >= ay) & (ax >= az))
    return PLANE_ANYX;
  if ((ay >= ax) & (ay >= az))
    return PLANE_ANYY;
  return PLANE_ANYZ;
}
#endif /* __PLANE_H__ */


